﻿// INGENIOUS FRAMEWORK - Semantic Web Development Simplified
// Copyright (C) 2012 Linked Data Tools

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

using Ingenious.Graph;
using Ingenious.Core;

namespace Ingenious.GraphStore
{
    /// <summary>
    /// Provides a client-only, in-memory based model store (not persisted)
    /// Useful for creating offline models, before merging into a persisted model or
    /// for times when you do not wish the lifetime to exceed the lifetime of the app
    /// 
    /// Also useful for general testing without requiring a persisted data store
    /// </summary>
    /// <typeparam name="TTripleId">The object ID type</typeparam>
    public abstract class MemoryGraphStore<TTripleId> : GraphStoreBase<TTripleId>
    {
        // A strong reference store (this will always keep the triples alive)
        private readonly Dictionary<string, Dictionary<TTripleId, Triple<TTripleId>>> m_graphTriplesById = new Dictionary<string, Dictionary<TTripleId, Triple<TTripleId>>>();

        // Graphs, by name
        private readonly Dictionary<string, Graph<TTripleId>> m_graphsByName = new Dictionary<string, Graph<TTripleId>>();

        #region Object Lifetime
        public MemoryGraphStore(MemoryGraphProxy<TTripleId> memoryClientProxy, MemoryGraphStoreConnectionInfo<TTripleId> graphStoreConnectionInfo)
            : base(memoryClientProxy, graphStoreConnectionInfo)
        {
        }

        public override void Dispose()
        {
            base.Dispose();

            foreach (Dictionary<TTripleId, Triple<TTripleId>> triplesById in m_graphTriplesById.Values)
            {
                triplesById.Clear();
            }

            m_graphTriplesById.Clear();
            m_graphsByName.Clear();
        }
        #endregion

        #region Overrides
        protected override IList<Tuple<string, NodePropertyInfo<TTripleId>>> InternalGetPropertyInfosForSubjectUri(Graph<TTripleId> graph, Uri subjectUri)
        {
            List<Tuple<string, NodePropertyInfo<TTripleId>>> nodePropertyInfos = new List<Tuple<string, NodePropertyInfo<TTripleId>>>();

            // Simply query the model for the child triples that reference this subject URI
            // For value types, we retrieve the value in the store immediately (not delay loaded)
            Dictionary<TTripleId, Triple<TTripleId>> triplesById = null;

            if(!m_graphTriplesById.TryGetValue(graph.Name, out triplesById))
            {
                throw new InvalidOperationException("Memory graph store does not contain graph with name '" + graph.Name + "'");
            }

            IEnumerable<Triple<TTripleId>> triplesWithSubjectUri = triplesById.Where(kv => kv.Value.Subject == subjectUri).Select(kv => kv.Value);

            foreach (Triple<TTripleId> triple in triplesWithSubjectUri)
            {
                NodePropertyInfo<TTripleId> nodePropertyInfo = new NodePropertyInfo<TTripleId>();

                nodePropertyInfo.ID = triple.ID;

                // If we have a value type, get the wrapping type. If an object reference, it is a reference type
                if (triple.IsLiteral)
                {
                    nodePropertyInfo.ItemWrappingInfo = TypeHelper.GetItemWrappingInfo(triple.Value.GetType());

                    // Literal values are not delay loaded in Ingenious Framework
                    nodePropertyInfo.LiteralValue = triple.Value;
                }
                else
                {
                    nodePropertyInfo.ItemWrappingInfo = new ItemWrappingInfo();
                    nodePropertyInfo.ItemWrappingInfo.ItemType = ItemType.OtherReference;
                }

                nodePropertyInfos.Add(new Tuple<string, NodePropertyInfo<TTripleId>>(triple.Predicate.ToString(), nodePropertyInfo));
            }

            return nodePropertyInfos;
        }

        protected override Triple<TTripleId> InternalLoadTripleById(string graphName, TTripleId id)
        {
            // Just returns the triple already stored in memory cache
            return WeakItemsCacheByGraphName[graphName][id].Target as Triple<TTripleId>;
        }

        protected override void SubmitInsertBatchToStore(Graph<TTripleId> graph, IEnumerable<Triple<TTripleId>> insertChanges)
        {
            Dictionary<TTripleId, Triple<TTripleId>> triplesById = null;

            if (!m_graphTriplesById.TryGetValue(graph.Name, out triplesById))
            {
                throw new GraphStoreException("Memory graph store does not contain graph with name '" + graph.Name + "'");
            }

            foreach (Triple<TTripleId> triple in insertChanges)
            {
                triplesById.Add(triple.ID, triple);
            }
        }

        protected override void SubmitUpdateBatchToStore(Graph<TTripleId> graph, IEnumerable<Triple<TTripleId>> updateChanges)
        {
            Dictionary<TTripleId, Triple<TTripleId>> triplesById = null;

            if (!m_graphTriplesById.TryGetValue(graph.Name, out triplesById))
            {
                throw new GraphStoreException("Memory graph store does not contain graph with name '" + graph.Name + "'");
            }

            foreach (Triple<TTripleId> triple in updateChanges)
            {
                Debug.Assert(!triplesById.ContainsKey(triple.ID));
                triplesById[triple.ID] = triple;
            }
        }

        protected override void SubmitRemoveBatchToStore(Graph<TTripleId> graph, IEnumerable<Triple<TTripleId>> removeChanges)
        {
            Dictionary<TTripleId, Triple<TTripleId>> triplesById = null;

            if (!m_graphTriplesById.TryGetValue(graph.Name, out triplesById))
            {
                throw new GraphStoreException("Memory graph store does not contain graph with name '" + graph.Name + "'");
            }

            foreach (Triple<TTripleId> triple in removeChanges)
            {
                Debug.Assert(!triplesById.ContainsKey(triple.ID));
                triplesById.Remove(triple.ID);
            }
        }

        protected override Graph<TTripleId> InternalGetGraph(string graphName)
        {
            Graph<TTripleId> graph = null;

            if (m_graphsByName.ContainsKey(graphName))
            {
                graph = m_graphsByName[graphName];
            }

            return graph;
        }

        protected override void InternalRegisterGraph(Graph<TTripleId> graph)
        {
            // Register the new graph
            if (m_graphsByName.ContainsKey(graph.Name))
            {
                throw new GraphStoreException("Memory graph store already contains graph with name '" + graph.Name + "'");
            }

            m_graphsByName[graph.Name] = graph;
            m_graphTriplesById[graph.Name] = new Dictionary<TTripleId, Triple<TTripleId>>();
        }

        protected override GraphQueryProviderBase CreateDefaultQueryProvider()
        {
            return null;
        }
        #endregion
    }
}
